Guard admin endpoints behind auth by default#443
Guard admin endpoints behind auth by default#443ChristianPavilonis wants to merge 4 commits intomainfrom
Conversation
There was a problem hiding this comment.
Summary
This PR correctly identifies and fixes the security gap where admin endpoints could be accessed without authentication when no handler is configured. The fail-closed approach is sound. However, the implementation adds complexity that may not be necessary given the existing handler-based auth system.
♻️ refactor
- Simplify: use config validation instead of runtime guard: The handler-based auth already enforces credentials correctly. Instead of adding
is_admin_path()and special-case logic inenforce_basic_auth, makeSettings::validate()return an error when no handler covers admin endpoints. This keepsenforce_basic_authunchanged, eliminates the dual-mechanism (is_admin_pathprefix check vsADMIN_ENDPOINTSlist), and catches misconfiguration at build time as an error rather than silently returning 401s at runtime. The existinguncovered_admin_endpoints()method is the right building block — call it fromvalidate()instead ofbuild.rs.
|
To clarify the refactor suggestion above — the required config is just a standard [[handlers]]
path = "^/admin"
username = "admin"
password = "a-strong-password"With the validation approach, |
|
Addressed the review feedback — refactored from runtime guard to build-time config validation:
All 472 tests pass, clippy clean, fmt clean. |
Admin paths (/admin/*) were only auth-gated when a configured handler's path regex happened to match them. The default config (^/secure) left /admin/keys/rotate and /admin/keys/deactivate publicly accessible. Now enforce_basic_auth always denies admin paths that no handler covers, and a startup warning alerts operators when no handler matches /admin/. Closes #400
P2: Check all admin endpoints (rotate + deactivate) in coverage
warning instead of only checking rotate.
P2: Move admin coverage warning from per-request main() to build
time via cargo:warning in build.rs, avoiding log noise in
Fastly Compute where every request is a fresh WASM instance.
P3: Guard exact /admin path in addition to /admin/ prefix so
future endpoints at that path are also protected.
Move admin endpoint protection from a runtime check in enforce_basic_auth to a build-time validation error in Settings::from_toml_and_env. This eliminates the dual-mechanism (is_admin_path prefix check vs handler-based auth) and catches misconfiguration at build time rather than silently returning 401s at runtime.
e3dd80c to
59a036e
Compare
aram356
left a comment
There was a problem hiding this comment.
Summary
Build-time config validation for admin endpoints is the right approach — misconfiguration becomes a compile error. The implementation is clean and well-tested. Two items need addressing before merge.
Blocking
🔧 wrench
from_toml()bypasses admin validation:from_toml()ispuband skips theuncovered_admin_endpoints()check entirely. Defense-in-depth: add the same check tofrom_toml()so no code path can construct aSettingswithout admin coverage. Cost is one trivialVecallocation. See inline comment onsettings.rs:377.ADMIN_ENDPOINTScan drift from router: The hardcoded list has no compile-time or test-time link to the actual route table inmain.rs:97-98. A new admin route added without updating this constant ships unprotected. See inline comment onsettings.rs:404.
Non-blocking
🌱 seedling
- Placeholder
changemepassword: No validation rejects weak handler passwords, unlike the synthetic secret key check. (trusted-server.toml:9)
♻️ refactor
- Nested
temp_env::with_var: 7-level nesting can be flattened withtemp_env::with_vars. (crates/common/src/settings.rs:841)
🤔 thinking
uncovered_admin_endpointsvisibility:pubis broader than needed —pub(crate)would suffice. (crates/common/src/settings.rs:412)
⛏ nitpick
- Comment says "fail" but means "panic":
.expect()panics, not returns an error. (crates/common/build.rs:31)
👍 praise
- Build-time validation approach: Shifting auth enforcement to config validation at compile time is architecturally sound. The
uncovered_admin_endpointsmethod and its test coverage (full/partial/no coverage) are thorough.
CI Status
- cargo fmt: PASS
- cargo test: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- CodeQL: PASS
Add admin endpoint validation to from_toml() so no code path can construct Settings without admin coverage. Add a compile-time sync test that verifies ADMIN_ENDPOINTS matches the Fastly router source. Also flatten nested temp_env::with_var calls, narrow visibility of uncovered_admin_endpoints to pub(crate), and fix build.rs comment.
Review feedback addressedAll comments from the second review have been addressed in 0ffad1b: Blocking
Non-blocking
All 474 tests pass, clippy clean, fmt clean. |
Summary
/admin/keys/rotateand/admin/keys/deactivatewere publicly accessible when no handler regex covered/admin/*pathsSettings::from_toml_and_env()now returns a hard error when no handler covers admin endpoints, catching misconfiguration at build timeenforce_basic_authremains a single, simple mechanism — admin protection is enforced by requiring handler coverage in the configurationChanges
crates/common/src/auth.rsenforce_basic_auth— removeis_admin_path()and runtime admin guard; admin paths use standard handler-based authcrates/common/src/settings.rsfrom_toml_and_env()errors whenuncovered_admin_endpoints()finds unprotected admin paths; update doc comments and testscrates/common/build.rscargo:warningloop (redundant —from_toml_and_envnow errors on uncovered admin endpoints)crates/common/src/test_support.rs^/adminhandler to base test settingstrusted-server.toml^/adminhandler to dev configCloses
Closes #400
Test plan
cargo test --workspace(472 tests pass)cargo clippy --all-targets --all-features -- -D warningscargo fmt --all -- --checkChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)